/*
	Copyright 2010 David Lerch

	This file is part of gtaformats.

	gtaformats is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	gtaformats is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with gtaformats.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "IMGArchive.h"
#include "IMGException.h"
#include <string>
#include <cstring>
#include <vector>
#include "../gf_filetype.h"

using std::string;
using std::vector;




IMGArchive::IMGArchive(InputStream* stream, bool randomAccess)
		: stream(stream), randomAccess(randomAccess), autoGeneratedStream(false)
{
	readHeader(stream);
}


IMGArchive::IMGArchive(const char* filename)
		: stream(NULL), randomAccess(true), autoGeneratedStream(true)
{
	string fname(filename);

	if (isValidDIRFilename(fname)) {
		string imgFile(fname);
		size_t index = imgFile.find_last_of('.');
		imgFile = imgFile.substr(0, index).append(".img");

		InputStream* dirStream = openStream(filename);

		if (!dirStream) {
			throw IMGException("Unable to open DIR file", __FILE__, __LINE__);
		}

		stream = openStream(imgFile.c_str());

		if (!stream) {
			char errmsg[2048];
			sprintf(errmsg, "Unable to open corresponding IMG file %s for the given DIR file",
					imgFile.c_str());
			throw IMGException(errmsg, __FILE__, __LINE__);
		}

		readHeader(dirStream);

		delete dirStream;
	} else if (isValidIMGFilename(fname)) {
		if (guessIMGVersion(filename) == VER2) {
			stream = openStream(filename);

			if (!stream) {
				throw IMGException("Unable to open VER2 IMG file", __FILE__, __LINE__);
			}

			readHeader(stream);
		} else {
			stream = openStream(filename);

			if (!stream) {
				throw IMGException("Unable to open VER1 IMG file", __FILE__, __LINE__);
			}

			string dirFile(fname);
			size_t index = dirFile.find_last_of('.');
			dirFile = dirFile.substr(0, index).append(".dir");

			InputStream* dirStream = openStream(dirFile.c_str());

			if (!dirStream) {
				char errmsg[2048];
				sprintf(errmsg, "Unable to open corresponding DIR file %s for the given IMG file",
						dirFile.c_str());
				throw IMGException(errmsg, __FILE__, __LINE__);
			}

			readHeader(dirStream);

			delete dirStream;
		}
	} else {
		throw IMGException("File name is neither an IMG nor a DIR file", __FILE__, __LINE__);
	}
}


IMGArchive::IMGArchive(InputStream* dirStream, InputStream* imgStream, bool randomAccess)
		: stream(imgStream), randomAccess(randomAccess), autoGeneratedStream(false)
{
	readHeader(dirStream);
}


IMGArchive::IMGArchive(const char* dirName, const char* imgName)
		: stream(NULL), randomAccess(true), autoGeneratedStream(true)
{
	stream = openStream(imgName);
	InputStream* dirStream = openStream(dirName);

	readHeader(dirStream);

	delete dirStream;
}


IMGArchive::~IMGArchive()
{
	for (int32_t i = 0 ; i < numEntries ; i++) {
		delete entries[i];
	}

	delete[] entries;

	if (autoGeneratedStream  &&  stream) {
		delete stream;
	}
}


bool IMGArchive::isValidDIRFilename(const std::string& filename)
{
	return GFGuessFileType(filename) == GF_TYPE_DIR;
}


bool IMGArchive::isValidIMGFilename(const std::string& filename)
{
	return GFGuessFileType(filename) == GF_TYPE_IMG;
}


IMGArchive::IMGVersion IMGArchive::guessIMGVersion(InputStream* stream, bool returnToStart)
{
	char fourCC[4];
	stream->read(fourCC, 4);

	if (stream->hasReachedEnd()) {
		return VER1;
	}

	if (returnToStart) {
		stream->seek(-4);
	}

	if (fourCC[0] == 'V'  &&  fourCC[1] == 'E'  &&  fourCC[2] == 'R'  &&  fourCC[3] == '2') {
		return VER2;
	} else {
		return VER1;
	}
}


IMGArchive::IMGVersion IMGArchive::guessIMGVersion(const char* filename)
{
	FileInputStream stream(filename, STREAM_BINARY);
	return guessIMGVersion(&stream, false);
}


InputStream* IMGArchive::gotoEntry(IMGEntry* entry) {
	long long start = entry->offset*IMG_BLOCK_SIZE;
	stream->seek(start - bytesRead);
	return stream;
}

InputStream* IMGArchive::gotoEntry(const char* name) {
	IMGEntry* entry = getEntryByName(name);

	if (!entry) {
		return NULL;
	}

	return gotoEntry(entry);
}

void IMGArchive::visit(IMGVisitor* visitor, IMGEntry* entry) {
	void* udata = NULL;

	if (visitor->readHeader(entry, udata)) {
		gotoEntry(entry);
		visitor->readEntry(entry, stream, udata);
	}
}

void IMGArchive::visitAll(IMGVisitor* visitor) {
	for (int i = 0 ; i < numEntries ; i++) {
		visit(visitor, entries[i]);
	}
}

IMGEntry* IMGArchive::getEntryByName(const char* name) {
	for (int32_t i = 0 ; i < numEntries ; i++) {
		if (strcmp(entries[i]->name, name) == 0) {
			return entries[i];
		}
	}

	return NULL;
}


void IMGArchive::readHeader(InputStream* stream)
{
	int32_t firstBytes;

	stream->read((char*) &firstBytes, 4);
	int gcount = stream->getLastReadCount();

	if (stream->hasReachedEnd()) {
		if (gcount == 0) {
			// This is an empty file: VER1 DIR files can be completely empty, so we assume this one is
			// such a file.

			version = VER1;
			numEntries = 0;
			entries = new IMGEntry*[0];
			return;
		} else {
			throw IMGException("Premature end of file", __FILE__, __LINE__);
		}
	}

	char* fourCC = (char*) &firstBytes;

	if (fourCC[0] == 'V'  &&  fourCC[1] == 'E'  &&  fourCC[2] == 'R'  &&  fourCC[3] == '2') {
		version = VER2;
		stream->read((char*) &numEntries, 4);

		entries = new IMGEntry*[numEntries];

		for (int32_t i = 0 ; i < numEntries ; i++) {
			entries[i] = new IMGEntry;
			stream->read((char*) entries[i], sizeof(IMGEntry));
		}

		bytesRead = 8 + numEntries*sizeof(IMGEntry);
	} else {
		version = VER1;

		vector<IMGEntry*> entryVector;

		IMGEntry* firstEntry = new IMGEntry;
		firstEntry->offset = firstBytes;
		stream->read((char*) firstEntry+4, sizeof(IMGEntry)-4);
		entryVector.push_back(firstEntry);

		numEntries = 0;

		while (!stream->hasReachedEnd()) {
			IMGEntry* entry = new IMGEntry;
			stream->read((char*) entry, sizeof(IMGEntry));
			entryVector.push_back(entry);
			numEntries++;
		}

		entries = new IMGEntry*[entryVector.size()];

		vector<IMGEntry*>::iterator it;
		int i = 0;
		for (it = entryVector.begin() ; it != entryVector.end() ; it++) {
			entries[i++] = *it;
		}

		bytesRead = 0;
	}

	for (int32_t i = 0 ; i < numEntries ; i++) {
		IMGEntry* entry = entries[i];

		if (entry->offset < 0) {
			throw IMGException("Entry offset is < 0!", __FILE__, __LINE__);
		}
		if (entry->size < 0) {
			throw IMGException("Entry size is < 0!", __FILE__, __LINE__);
		}
	}
}

InputStream* IMGArchive::openStream(const char* filename)
{
	FileInputStream* stream = new FileInputStream(filename, STREAM_BINARY);
	return stream;
}


void IMGArchive::reposition(int offset)
{
	bytesRead += offset;
}
